/*
 * Exploder
 * 
 * Tero Pyylampi (218437), Jussi Tuurinkoski (211594)
 *
 * Main game engine
 */

#include "GameEngine.h"
#include "GameScene.h"
#include "layers/LayerGame.h"
#include "misc/GameLevelEngine.h"
#include "misc/GameContactListener.h"
#include "misc/GameClouds.h"
#include "objects/GameAbstractEngine.h"
#include "objects/physicobjects/GamePhysicObjectEngine.h"
#include "objects/physicobjects/GamePhysicObject.h"
#include "objects/physicobjects/elements/GameConstructionElement.h"
#include "../Common.h"
#include "../MainEngine.h"
#include "../LuaInterface.h"
#include "../misc/UserData.h"

// default constructor
GameEngine::GameEngine( GameScene *scene, GameCamera *camera, LayerGame *layer ) 
	: mGameScene( scene ), mGameCamera( camera ), mGameLayer( layer ), mOldGameState( GAME_RUNNING ) {
	// initialize engine vector
	mEngines = new std::vector< GameAbstractEngine* >();

	// initialize physics world
	b2Vec2 gravity( 0, -10.0f );
	mGameWorld = new b2World( gravity );
	mGameWorld->SetAllowSleeping( true );
	mGameWorld->SetContinuousPhysics( true );

	// initialize contact listener
	mGameWorld->SetContactListener( new GameContactListener( this ) );
	
	// physic object engine
	mPhysicObjectEngine = new GamePhysicObjectEngine();
	mEngines->push_back( mPhysicObjectEngine );

	// create ground
	mPhysicObjectEngine->createGroundObject( mGameWorld, mGameLayer );

	// initialize level engine & create level
	mGameLevelEngine = new GameLevelEngine( mGameScene->getMaster()->getLuaInterface(), this, mGameCamera );
	mGameLevelEngine->startLevel( mGameScene->getCurrentLevel() );

	// create cloud handler
	mGameClouds = new GameClouds( mGameScene->getLayerBackground() );

	// TODO: starting money should depend on level!!!
	mCurrentMoney = mGameScene->getMaster()->getUserData()->getGameData()->currentMoney + 4000;
}

// default destructor
GameEngine::~GameEngine() {
	for( unsigned int i = 0; i < mEngines->size(); ++i )
		delete mEngines->at( i );
	delete mEngines;
}

// update -function
void GameEngine::update( cocos2d::ccTime dt ) {
	// update physics world with a fixed timestep (60fps)
	mGameWorld->Step( 0.01666f, BOX2D_VELOCITY_ITERATIONS, BOX2D_POSITION_ITERATIONS );

	// update level engine
	mGameLevelEngine->update( dt );
	// update all the different engines
	for( unsigned int i = 0; i < mEngines->size(); ++i )
		mEngines->at( i )->update( dt );

	// update clouds
	mGameClouds->update( dt );

	// check game state
	if( mOldGameState == GAME_RUNNING ) {
		GameState newGameState = checkGameState();
		if( newGameState == GAME_WON ) 
			mGameScene->showLevelClearedDialog();
		else if( newGameState == GAME_LOST )
			mGameScene->showLevelFailedDialog();
		mOldGameState = newGameState;
	}
}

// get master scene
GameScene *GameEngine::getGameScene() {
	return mGameScene;
}

// get game camera
GameCamera *GameEngine::getGameCamera() {
	return mGameCamera;
}
// get master layer
LayerGame *GameEngine::getGameLayer() {
	return mGameLayer;
}

// get physics world
b2World *GameEngine::getGameWorld() {
	return mGameWorld;
}

// get physic object engine
GamePhysicObjectEngine *GameEngine::getPhysicObjectEngine() {
	return mPhysicObjectEngine;
}

// get current game state
GameState GameEngine::getCurrentGameState() {
	return mOldGameState;
}

// get current money
int GameEngine::getCurrentMoney() {
	return mCurrentMoney;
}

// modify current money
void GameEngine::modifyCurrentMoney( int amount ) {
	mCurrentMoney += amount;
}

// check for winning conditions
GameState GameEngine::checkGameState() {
	std::vector< GamePhysicObject* > *o = getPhysicObjectEngine()->getPhysicObjects();

	// check whether the player has broken something valuable
	for( unsigned int i = 0; i < o->size(); ++i ) {
		if( o->at( i )->getObjectType() < ELEMENT_INVALID && o->at( i )->getObjectType() != ELEMENT_GROUND &&
			dynamic_cast< GameConstructionElement* >( o->at( i ) )->getElementStateType() == SHOULD_NOT_MOVE &&
			dynamic_cast< GameConstructionElement* >( o->at( i ) )->isThisObjectBrokenOrMoved() ) {
			return GAME_LOST;
		}
	}

	// check if there is a single object which has not yet moved or the simulation is still running
	bool objectStillMoving = false;
	bool objectPreventingVictory = false;
	for( unsigned int i = 0; i < o->size(); ++i ) {
		if( o->at( i )->getObjectType() < ELEMENT_INVALID && o->at( i )->getObjectType() != ELEMENT_GROUND ) {
			if( dynamic_cast< GameConstructionElement* >( o->at( i ) )->isThisObjectPreventingVictory() )
				objectPreventingVictory = true;
			if( o->at( i )->currentlyAsleep() == false )
				objectStillMoving = true;
		}
	}

	// check if there are bombs in the field
	int bombsInTheField = 0;
	for( unsigned int i = 0; i < o->size(); ++i )
		if( o->at( i )->getObjectType() >= BOMB_SMALL_MINE && o->at( i )->getObjectType() < BOMB_INVALID )
			++bombsInTheField;

	// running out of money
	if( mCurrentMoney < 750 && objectStillMoving == false && objectPreventingVictory == true && bombsInTheField == 0 )
		return GAME_LOST;
	// we have won the game
	else if( objectStillMoving == false && objectPreventingVictory == false )
		return GAME_WON;
	// normally continue
	else
		return GAME_RUNNING;
}
